home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / pyshared / UpdateManager / ChangelogViewer.py < prev    next >
Encoding:
Python Source  |  2009-04-27  |  10.5 KB  |  264 lines

  1. # ReleaseNotesViewer.py
  2. #  
  3. #  Copyright (c) 2006 Sebastian Heinlein
  4. #                2007 Canonical
  5. #  
  6. #  Author: Sebastian Heinlein <sebastian.heinlein@web.de>
  7. #          Michael Vogt <michael.vogt@ubuntu.com>
  8. #
  9. #  This modul provides an inheritance of the gtk.TextView that is 
  10. #  aware of http URLs and allows to open them in a browser.
  11. #  It is based on the pygtk-demo "hypertext".
  12. #  This program is free software; you can redistribute it and/or 
  13. #  modify it under the terms of the GNU General Public License as 
  14. #  published by the Free Software Foundation; either version 2 of the
  15. #  License, or (at your option) any later version.
  16. #  This program is distributed in the hope that it will be useful,
  17. #  but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  19. #  GNU General Public License for more details.
  20. #  You should have received a copy of the GNU General Public License
  21. #  along with this program; if not, write to the Free Software
  22. #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  23. #  USA
  24.  
  25.  
  26. import pygtk
  27. import gtk
  28. import pango
  29. import subprocess
  30. import os
  31. from gettext import gettext as _
  32.  
  33. class ChangelogViewer(gtk.TextView):
  34.     def __init__(self, changelog=None):
  35.         """Init the ChangelogViewer as an Inheritance of the gtk.TextView"""
  36.         # init the parent
  37.         gtk.TextView.__init__(self)
  38.         # global hovering over link state
  39.         self.hovering = False
  40.         self.first = True
  41.         # setup the buffer and signals
  42.         self.set_property("editable", False)
  43.         self.set_cursor_visible(False)
  44.         # set some margin
  45.         self.set_right_margin(4)
  46.         self.set_left_margin(4)
  47.         self.set_pixels_above_lines(4)
  48.         self.buffer = gtk.TextBuffer()
  49.         self.set_buffer(self.buffer)
  50.         self.connect("button-press-event", self.button_press_event)
  51.         self.connect("motion-notify-event", self.motion_notify_event)
  52.         self.connect("visibility-notify-event", self.visibility_notify_event)
  53.         #self.buffer.connect("changed", self.search_links)
  54.         self.buffer.connect_after("insert-text", self.on_insert_text)
  55.         # search for links in the changelog and make them clickable
  56.         if changelog != None:
  57.             self.buffer.set_text(changelog)
  58.             
  59.     def create_context_menu(self, url):
  60.         """Create the context menu to be displayed when links are right clicked"""
  61.         self.menu = gtk.Menu()
  62.         
  63.         # create menu items
  64.         item_grey_link = gtk.MenuItem(url)
  65.         item_grey_link.connect("activate", self.handle_context_menu, "open", url)
  66.         item_seperator = gtk.MenuItem()
  67.         item_open_link = gtk.MenuItem(_("Open Link in Browser"))
  68.         item_open_link.connect("activate", self.handle_context_menu, "open", url)
  69.         item_copy_link = gtk.MenuItem(_("Copy Link to Clipboard"))
  70.         item_copy_link.connect("activate", self.handle_context_menu, "copy", url)
  71.         
  72.         # add menu items
  73.         self.menu.add(item_grey_link)
  74.         self.menu.add(item_seperator)
  75.         self.menu.add(item_open_link)
  76.         self.menu.add(item_copy_link)
  77.         self.menu.show_all()
  78.     
  79.     def handle_context_menu(self, menuitem, action, url):
  80.         """Handle activate event for the links' context menu"""
  81.         if action == "open":
  82.             self.open_url(url)
  83.         if action == "copy":
  84.             cb = gtk.Clipboard()
  85.             cb.set_text(url)
  86.             cb.store()
  87.  
  88.     def tag_link(self, start, end, url):
  89.         """Apply the tag that marks links to the specified buffer selection"""
  90.         tagged = False
  91.         tags = start.get_tags()
  92.         for tag in tags:
  93.             url = tag.get_data("url")
  94.             if url != "":
  95.                 return
  96.         tag = self.buffer.create_tag(None, foreground="blue",
  97.                                      underline=pango.UNDERLINE_SINGLE)
  98.         tag.set_data("url", url)
  99.         self.buffer.apply_tag(tag , start, end)
  100.  
  101.     def on_insert_text(self, buffer, iter_end, text, *args):
  102.         """Search for http URLs in newly inserted text  
  103.            and tag them accordingly"""
  104.  
  105.         # some convenient urls
  106.         MALONE = "https://launchpad.net/bugs/"
  107.         DEBIAN = "http://bugs.debian.org/"
  108.         CVE = "http://cve.mitre.org/cgi-bin/cvename.cgi?name="
  109.         # some convinient end-markers
  110.         ws = [" ","\t","\n"]
  111.         brak = [")","]",">"]
  112.         punct = [",","!",":"]
  113.         dot = ["."]+punct
  114.         # search items are start-str, list-of-end-strs, url-prefix
  115.         # a lot of this search is "TEH SUCK"(tm) because of limitations
  116.         # in iter.forward_search()
  117.         # - i.e. no insensitive searching, no regexp
  118.         search_items = [ ("http://", ws+brak+punct, "http://"),
  119.                          ("LP#", ws+brak+dot, MALONE),
  120.                          ("LP: #", ws+brak+dot, MALONE),
  121.                          ("lp: #", ws+brak+dot, MALONE),
  122.                          ("LP:#",  ws+brak+dot, MALONE),
  123.                          ("Malone: #", ws+brak+dot, MALONE),
  124.                          ("Malone:#", ws+brak+dot, MALONE),
  125.                          ("Ubuntu: #", ws+brak+dot, MALONE),
  126.                          ("Ubuntu:#", ws+brak+dot, MALONE),
  127.                          ("Closes: #",ws+brak+dot, DEBIAN),
  128.                          ("Closes:#",ws+brak+dot, DEBIAN),
  129.                          ("closes:#",ws+brak+dot, DEBIAN),
  130.                          ("closes: #",ws+brak+dot, DEBIAN),
  131.                          ("CVE-", ws+brak+dot, CVE),
  132.                        ]
  133.         # init
  134.         iter = buffer.get_iter_at_offset(iter_end.get_offset() - len(text))
  135.         iter_real_end = buffer.get_end_iter()
  136.  
  137.         # search for the next match in the buffer
  138.         for (start_str, end_list, url_prefix) in search_items:
  139.             while True:
  140.                 ret = iter.forward_search(start_str,
  141.                                           gtk.TEXT_SEARCH_VISIBLE_ONLY,
  142.                                           iter_end)
  143.                 # if we reach the end break the loop
  144.                 if not ret:
  145.                     break
  146.                 # get the position of the protocol prefix
  147.                 (match_start, match_end) = ret
  148.                 match_suffix = match_end.copy()
  149.                 match_tmp = match_end.copy()
  150.                 while True:
  151.                     # extend the selection to the complete search item
  152.                     if match_tmp.forward_char():
  153.                         text =  match_end.get_text(match_tmp)
  154.                         if text in end_list:
  155.                             break
  156.                     else:
  157.                         break
  158.                     match_end = match_tmp.copy()
  159.  
  160.                 # call the tagging method for the complete URL
  161.                 url = url_prefix + match_suffix.get_text(match_end)
  162.  
  163.                 self.tag_link(match_start, match_end, url)
  164.                 # set the starting point for the next search
  165.                 iter = match_end
  166.  
  167.     def button_press_event(self, text_view, event):
  168.         """callback for mouse click events"""
  169.         # we only react on left or right mouse clicks
  170.         if event.button != 1 and event.button != 3:
  171.             return False
  172.  
  173.         # try to get a selection
  174.         try:
  175.             (start, end) = self.buffer.get_selection_bounds()
  176.         except ValueError:
  177.             pass
  178.         else:
  179.             if start.get_offset() != end.get_offset():
  180.                 return False
  181.  
  182.         # get the iter at the mouse position
  183.         (x, y) = self.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET,
  184.                                               int(event.x), int(event.y))
  185.         iter = self.get_iter_at_location(x, y)
  186.         
  187.         # call open_url or menu.popup if an URL is assigned to the iter
  188.         tags = iter.get_tags()
  189.         for tag in tags:
  190.             url = tag.get_data("url")
  191.             if url != None:
  192.                 if event.button == 1: 
  193.                     self.open_url(url)
  194.                     break
  195.                 if event.button == 3:
  196.                     self.create_context_menu(url)
  197.                     self.menu.popup(None, None, None, event.button, event.time)
  198.                     return True
  199.  
  200.     def open_url(self, url):
  201.         """Open the specified URL in a browser"""
  202.         # Find an appropiate browser
  203.         if os.path.exists("/usr/bin/exo-open"):
  204.             command = ["exo-open", url]
  205.         elif os.path.exists('/usr/bin/gnome-open'):
  206.             command = ['gnome-open', url]
  207.         else:
  208.             command = ['x-www-browser', url]
  209.  
  210.         # Avoid to run the browser as user root
  211.         if os.getuid() == 0 and os.environ.has_key('SUDO_USER'):
  212.             command = ['sudo', '-u', os.environ['SUDO_USER']] + command
  213.  
  214.         subprocess.Popen(command)
  215.  
  216.     def motion_notify_event(self, text_view, event):
  217.         """callback for the mouse movement event, that calls the
  218.            check_hovering method with the mouse postition coordiantes"""
  219.         x, y = text_view.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET,
  220.                                                  int(event.x), int(event.y))
  221.         self.check_hovering(x, y)
  222.         self.window.get_pointer()
  223.         return False
  224.     
  225.     def visibility_notify_event(self, text_view, event):
  226.         """callback if the widgets gets visible (e.g. moves to the foreground)
  227.            that calls the check_hovering method with the mouse position
  228.            coordinates"""
  229.         (wx, wy, mod) = text_view.window.get_pointer()
  230.         (bx, by) = text_view.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, wx,
  231.                                                      wy)
  232.         self.check_hovering(bx, by)
  233.         return False
  234.  
  235.     def check_hovering(self, x, y):
  236.         """Check if the mouse is above a tagged link and if yes show
  237.            a hand cursor"""
  238.         _hovering = False
  239.         # get the iter at the mouse position
  240.         iter = self.get_iter_at_location(x, y)
  241.         
  242.         # set _hovering if the iter has the tag "url"
  243.         tags = iter.get_tags()
  244.         for tag in tags:
  245.             url = tag.get_data("url")
  246.             if url != None:
  247.                 _hovering = True
  248.                 break
  249.  
  250.         # change the global hovering state
  251.         if _hovering != self.hovering or self.first == True:
  252.             self.first = False
  253.             self.hovering = _hovering
  254.             # Set the appropriate cursur icon
  255.             if self.hovering:
  256.                 self.get_window(gtk.TEXT_WINDOW_TEXT).\
  257.                         set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
  258.             else:
  259.                 self.get_window(gtk.TEXT_WINDOW_TEXT).\
  260.                         set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
  261.